/**
 * This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
 */
package com.softwarecraftsmen.orogeny.filing;

import static com.softwarecraftsmen.orogeny.filing.AbsoluteDirectory.CurrentWorkingDirectory;
import static com.softwarecraftsmen.orogeny.filing.AbsoluteDirectory.JavaHomeDirectory;
import static com.softwarecraftsmen.orogeny.filing.fileSystems.AbstractFileSystem.CurrentFileSystem;
import com.softwarecraftsmen.orogeny.filing.findFileFilters.FindFilesFilter;
import com.softwarecraftsmen.orogeny.filing.findSubdirectoriesFilters.FindSubdirectoriesFilter;
import static com.softwarecraftsmen.orogeny.operatingSystems.OperatingSystemFamily.currentOperatingSystemFamily;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.File;
import java.io.StringWriter;
import static java.lang.System.getProperty;
import static java.lang.management.ManagementFactory.getRuntimeMXBean;
import java.lang.management.RuntimeMXBean;
import java.util.Collections;
import static java.util.Collections.emptySet;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.Iterator;

public class AbsoluteDirectories implements AbsolutePaths, Iterable<AbsoluteDirectory>
{
	@NotNull
	public static final AbsoluteDirectories EmptyAbsoluteDirectories = new AbsoluteDirectories(Collections.<AbsoluteDirectory>emptySet());

	@NotNull
	public static final AbsoluteDirectories JavaClassPath = absoluteDirectoriesFromSystemProperty("java.class.path", EmptyAbsoluteDirectories);

	@NotNull
	public static final AbsoluteDirectories JavaLibraryPath = absoluteDirectoriesFromSystemProperty("java.library.path", EmptyAbsoluteDirectories);

	@NotNull
	public static final AbsoluteDirectories JavaBootClassPath;

	static
	{
		final RuntimeMXBean runtimeMXBean = getRuntimeMXBean();
		JavaBootClassPath = runtimeMXBean.isBootClassPathSupported() ? CurrentFileSystem.toPaths(CurrentWorkingDirectory, runtimeMXBean.getBootClassPath()) : EmptyAbsoluteDirectories;
	}

	@NotNull
	public static final AbsoluteDirectories JavaEndorsedDirectories = absoluteDirectoriesFromSystemProperty("java.endorsed.dirs", absoluteDirectories(JavaHomeDirectory.subDirectory("lib", "endorsed")));

	@NotNull
	public static final AbsoluteDirectories JavaExtensionDirectories = absoluteDirectoriesFromSystemProperty("java.ext.dirs", absoluteDirectories(JavaHomeDirectory.subDirectory("lib", "ext")));

	@NotNull
	public static final AbsoluteDirectories PATH = safeAbsoluteDirectoriesFromEnvironmentVariable("PATH");

	@NotNull
	public static final AbsoluteDirectories CLASSPATH = safeAbsoluteDirectoriesFromEnvironmentVariable("CLASSPATH");

	@NotNull
	public static final AbsoluteDirectories MANPATH = safeAbsoluteDirectoriesFromEnvironmentVariable("MANPATH");

	private final Set<AbsoluteDirectory> directories;
	private static final Set<AbsoluteFile> EmptyFiles = emptySet();

	public AbsoluteDirectories(final @NotNull Set<AbsoluteDirectory> directories)
	{
		this.directories = directories;
	}

	@NotNull
	public AbsolutePaths and(final @NotNull AbsolutePath absolutePath)
	{
		getRuntimeMXBean().getBootClassPath();
		if (absolutePath instanceof AbsoluteDirectory)
		{
			return and((AbsoluteDirectory)absolutePath);
		}
		return new AbsoluteDirectoriesAndFiles(new LinkedHashSet<AbsolutePath>(directories)
		{{
			add(absolutePath);
		}});
	}

	@NotNull
	public AbsoluteDirectories and(final @NotNull AbsoluteDirectory absoluteDirectory)
	{
		return new AbsoluteDirectories(new LinkedHashSet<AbsoluteDirectory>(directories)
		{{
			add(absoluteDirectory);
		}});
	}

	@NotNull
	public AbsoluteDirectoriesAndFiles and(final @NotNull AbsoluteFile absoluteFile)
	{
		return new AbsoluteDirectoriesAndFiles(new LinkedHashSet<AbsolutePath>(directories)
		{{
			add(absoluteFile);
		}});
	}

	@NotNull
	public AbsoluteDirectoriesAndFiles and(final @NotNull AbsolutePaths absolutePaths)
	{
		return new AbsoluteDirectoriesAndFiles(new LinkedHashSet<AbsolutePath>(directories)
		{{
			addAll(absolutePaths.directories());
			addAll(absolutePaths.files());
		}});
	}

	@NotNull
	public AbsoluteDirectories and(final @NotNull AbsoluteDirectories absoluteDirectories)
	{
		return new AbsoluteDirectories(new LinkedHashSet<AbsoluteDirectory>(directories)
		{{
			addAll(absoluteDirectories.directories);
		}});
	}

	@NotNull
	public AbsoluteDirectoriesAndFiles and(final @NotNull AbsoluteFiles absoluteFiles)
	{
		return new AbsoluteDirectoriesAndFiles(new LinkedHashSet<AbsolutePath>(directories)
		{{
			addAll(absoluteFiles.files());
		}});
	}

	@NotNull
	public AbsoluteDirectoriesAndFiles and(final @NotNull AbsoluteDirectoriesAndFiles absoluteDirectories)
	{
		return new AbsoluteDirectoriesAndFiles(new LinkedHashSet<AbsolutePath>(directories)
		{{
			addAll(absoluteDirectories.directories());
			addAll(absoluteDirectories.files());
		}});
	}

	@NotNull
	public String toFileSystemSpecificPath()
	{
		final StringWriter writer = new StringWriter();
		boolean afterFirst = false;
		for (AbsoluteDirectory directory : directories)
		{
			if (afterFirst)
			{
				writer.write(directory.toFileSystemSpecificPathPrefixedByPathSeparator());
			}
			else
			{
				writer.write(directory.toFileSystemSpecificPath());
			}
			afterFirst = true;
		}
		return writer.toString();
	}

	public boolean exists()
	{
		for (AbsoluteDirectory directory : directories)
		{
			if (!directory.exists())
			{
				return false;
			}
		}
		return true;
	}

	@NotNull
	public Set<File> toFiles()
	{
		return new LinkedHashSet<File>()
		{{
			for (AbsoluteDirectory directory : directories)
			{
				add(directory.toFile());
			}
		}};
	}

	@NotNull
	public Set<AbsoluteFile> findFiles(final @NotNull FindFilesFilter findFilesFilter)
	{
		return new LinkedHashSet<AbsoluteFile>()
		{{
			for (AbsoluteDirectory directory : directories)
			{
				addAll(directory.findFiles(findFilesFilter));
			}
		}};
	}

	@NotNull
	public Set<AbsoluteDirectory> findSubdirectories(final @NotNull FindSubdirectoriesFilter findSubdirectoriesFilter)
	{
		return new LinkedHashSet<AbsoluteDirectory>()
		{{
			for (AbsoluteDirectory directory : directories)
			{
				addAll(directory.findSubdirectories(findSubdirectoriesFilter));
			}
		}};
	}

	@NotNull
	public Set<AbsoluteFile> files()
	{
		return EmptyFiles;
	}

	@NotNull
	public Set<AbsoluteDirectory> directories()
	{
		return new LinkedHashSet<>(directories);
	}

	public boolean isEmpty()
	{
		return directories.isEmpty();
	}

	@NotNull
	public String toString()
	{
		return toFileSystemSpecificPath();
	}

	public boolean equals(final @Nullable Object o)
	{
		if (this == o)
		{
			return true;
		}
		if (o == null || getClass() != o.getClass())
		{
			return false;
		}

		final AbsoluteDirectories that = (AbsoluteDirectories) o;
		return directories.equals(that.directories);
	}

	public int hashCode()
	{
		return directories.hashCode();
	}

	@NotNull
	public static AbsoluteDirectories absoluteDirectories(final @NotNull AbsoluteDirectory currentWorkingDirectory, final @NotNull String pathsDescription)
	{
		return currentWorkingDirectory.parsePathsDescription(pathsDescription);
	}

	@SuppressWarnings({"ManualArrayToCollectionCopy"})
	@NotNull
	public static AbsoluteDirectories absoluteDirectories(final @NotNull AbsoluteDirectory... absoluteDirectories)
	{
		return new AbsoluteDirectories(new LinkedHashSet<AbsoluteDirectory>()
		{{
			for (AbsoluteDirectory absoluteDirectory : absoluteDirectories)
			{
				add(absoluteDirectory);
			}
		}});
	}

	@SuppressWarnings({"ManualArrayToCollectionCopy"})
	@NotNull
	public static AbsoluteDirectories absoluteDirectories(final @NotNull AbsoluteDirectories... absoluteDirectories)
	{
		final AbsoluteDirectories actual;
		if (absoluteDirectories.length == 0)
		{
			actual = EmptyAbsoluteDirectories;
		}
		else
		{
			AbsoluteDirectories nearActual = absoluteDirectories[0];
			for (int index = 1; index < absoluteDirectories.length; index++)
			{
				final AbsoluteDirectories fromDirectories = absoluteDirectories[index];
				nearActual = nearActual.and(fromDirectories);
			}
			actual = nearActual;
		}
		return actual;
	}

	@NotNull
	public static AbsoluteDirectories absoluteDirectoriesFromEnvironmentVariable(final @NotNull String environmentVariableNameCaseSensitivityHandled)
	{
		final String path = currentOperatingSystemFamily().getEnvironmentVariableWithCaseSensitivityPerPlatform(environmentVariableNameCaseSensitivityHandled);
		if (path == null)
		{
			return EmptyAbsoluteDirectories;
		}
		else
		{
			return CurrentFileSystem.toPaths(CurrentWorkingDirectory, path);
		}
	}

	@NotNull
	public static AbsoluteDirectories absoluteDirectoriesFromSystemProperty(final @NotNull String systemPropertyName, final @NotNull AbsoluteDirectories defaultValue)
	{
		final String directories = getProperty(systemPropertyName, null);
		if (directories == null)
		{
			return defaultValue;
		}
		else
		{
			return CurrentFileSystem.toPaths(CurrentWorkingDirectory, directories);
		}
	}

	@NotNull
	private static AbsoluteDirectories safeAbsoluteDirectoriesFromEnvironmentVariable(final @NotNull String environmentVariableNameCaseSensitivityHandled)
	{
		AbsoluteDirectories result;
		try
		{
			result = absoluteDirectoriesFromEnvironmentVariable(environmentVariableNameCaseSensitivityHandled);
		}
		catch (Exception exception)
		{
			result = EmptyAbsoluteDirectories;
		}
		return result;
	}

	@NotNull
	public Iterator<AbsoluteDirectory> iterator()
	{
		return directories.iterator();
	}
}
